﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Web.Mvc;

using Lilium.Web.Mvc.State;
using Lilium.CSharp.Expressions;
using Lilium.CSharp.Expressions.Substitutions;

namespace Lilium.Web.Mvc.EntityFilters
{
    public interface IAlphabet : IEntityFilter, IStatefulObject
    {
        string FieldName { get; set; }

        char FirstLetter { get; set; }

        char LastLetter { get; set; }
    }

    public static class Alphabet
    {
        public const string Action_All = "all";
        public const string Action_NoneAlpha = "nonAlpha";

        public static IAlphabet GetAplhabet(ControllerBase controller, Type entityType)
        {
            if (controller == null) throw new ArgumentNullException("controller");

            return (IAlphabet)controller.GetService(GetAplhabetType(entityType), true);
        }

        public static Type GetAplhabetType(Type entityType)
        {
            if (entityType == null) throw new ArgumentNullException("entityType");

            return typeof(Alphabet<>).MakeGenericType(entityType);
        }
    }

    public sealed class Alphabet<TEntity> : EntityFilter<TEntity, AlphabetState>, IAlphabet
    {
        public Alphabet(ControllerBase controller)
            : base(controller)
        {
        }

        public override IQueryable<TEntity> Apply(IQueryable<TEntity> entities)
        {
            if (entities == null) throw new ArgumentNullException("entities");

            if (State.NonAlpha)
            {
                var nonAplpha = Transformers.Transform<Func<TEntity, bool>>(
                    e => e.PropertyOrField<string>(FieldName)[0] < FirstLetter
                        || e.PropertyOrField<string>(FieldName)[0] > LastLetter);

                entities = entities.Where(nonAplpha);
            }
            else if (!string.IsNullOrEmpty(State.Prefix))
            {
                var prefix = Transformers.Transform<Func<TEntity, bool>>(
                    e => e.PropertyOrField<string>(FieldName).StartsWith(State.Prefix));

                entities = entities.Where(prefix);
            }

            return entities;
        }

        #region IAplhabet Members

        #region public string FieldName

        private string m_FieldName;

        public string FieldName
        {
            get { return m_FieldName; }
            set { m_FieldName = value; }
        }

        #endregion

        #region public char FirstLetter

        private char m_FirstLetter = 'a';

        public char FirstLetter
        {
            get { return m_FirstLetter; }
            set { m_FirstLetter = value; }
        }

        #endregion

        #region public char LastLetter

        private char m_LastLetter = 'z';

        public char LastLetter
        {
            get { return m_LastLetter; }
            set { m_LastLetter = value; }
        }

        #endregion

        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="actionName"></param>
        /// <returns></returns>
        /// <remarks>
        /// Запрос на GetState() происходит даже в случае, когда состояние запрашивает другой элемент, для 
        /// того, чтобы сформировать свой ActionLink. При этом actionName = null. В этом случае следует 
        /// вернуть текущее состояние объекта.
        /// </remarks>
        protected override AlphabetState OnGetState(string actionName)
        {
            if (!string.IsNullOrWhiteSpace(actionName))
                switch (actionName)
                {
                    case Alphabet.Action_All:
                        return new AlphabetState { NonAlpha = false, Prefix = "" };
                    case Alphabet.Action_NoneAlpha:
                        return new AlphabetState { NonAlpha = true, Prefix = "" };
                    default:
                        return new AlphabetState { NonAlpha = false, Prefix = actionName };
                }
            else
                return base.OnGetState(actionName);
        }
    }

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    public sealed class AlphabetAttribute : EntityFilterAttribute
    {
        public AlphabetAttribute(Type entityType, string fieldName)
            : base(Alphabet.GetAplhabetType(entityType))
        {
            if (fieldName == null) throw new ArgumentNullException("fieldName");

            m_FieldName = fieldName;
        }

        #region public string FieldName

        private readonly string m_FieldName;

        public string FieldName
        {
            get
            {
                return m_FieldName;
            }
        }

        #endregion

        #region public char FirstLetter

        private char m_FirstLetter = 'a';

        public char FirstLetter
        {
            get { return m_FirstLetter; }
            set { m_FirstLetter = value; }
        }

        #endregion

        #region public char LastLetter

        private char m_LastLetter = 'z';

        public char LastLetter
        {
            get { return m_LastLetter; }
            set { m_LastLetter = value; }
        }

        #endregion

        protected override IEntityFilter CreateFilter(ActionExecutingContext filterContext)
        {
            var aplhabet = (IAlphabet)filterContext.Controller.GetService(this.FilterType, true);
            aplhabet.FieldName = FieldName;
            aplhabet.FirstLetter = FirstLetter;
            aplhabet.LastLetter = LastLetter;
            return aplhabet;
        }
    }

    public sealed class AlphabetState : ICloneable, IEquatable<AlphabetState>
    {
        public AlphabetState()
        {
        }

        public AlphabetState(AlphabetState state)
        {
            if (state != null)
            {
                m_NonAlpha = state.NonAlpha;
                m_Prefix = state.Prefix;
            }
        }

        #region public bool NonAlpha

        private bool m_NonAlpha;

        public bool NonAlpha
        {
            get { return m_NonAlpha; }
            set { m_NonAlpha = value; }
        }

        #endregion

        #region public string Prefix

        private string m_Prefix = "";

        public string Prefix
        {
            get { return m_Prefix; }
            set { m_Prefix = value; }
        }

        #endregion

        public override bool Equals(object obj)
        {
            var other = obj as AlphabetState;
            if (other != null)
                return Equals(other);
            else
                return base.Equals(obj);
        }

        #region ICloneable Members

        public object Clone()
        {
            return this.MemberwiseClone();
        }

        #endregion

        #region IEquatable<AlphabetState> Members

        public bool Equals(AlphabetState other)
        {
            if (other != null)
                return (this.NonAlpha == other.NonAlpha) && (this.Prefix == other.Prefix);
            else
                return false;
        }

        #endregion
    }
}
